
#include <algorithm>
#include <eosiolib/name.hpp>
#include <eosiolib/action.hpp>
#include <eosiolib/transaction.hpp>
#include <eosio.token.hpp>
#include "utils.hpp"

class newn : public contract {
public:
	newn( eosio::name receiver, eosio::name code, eosio::datastream<const char*> ds )
	: eosio::contract(receiver, code, ds)
	,_global(receiver, code.value)
	,_bets(receiver, _self.value)
	,_hash(receiver, code.value)
	,_fund_pool(receiver, code.value)
	{
		print("^^^^^:reciever:", name{receiver}, " ,code:", name{code}, ",scopeA:", code.value, ",scopeB:", _self.value);
	}

	void transfer(const name& from, const name& to, const asset& quantity, const string& memo);

	[[eosio::action]]
	void receipt(const stbet& bet);

	[[eosio::action]]
	void reveal(const uint64_t& id, const capi_checksum256& seed);

	[[eosio::action]]
	void hil( name user ) {
		print( "^^^:Helle, ", name{user});
	}

    [[eosio::action]]
    void droptable(){
        tbbets_t _bets(_self, _self.value); // code, scope
        for(auto itr = _bets.begin(); itr != _bets.end();) {
            print("^^erase:", itr->id);
            itr = _bets.erase(itr);
        }
    }

private:
	tbbets_t _bets;
	tbfundpool_t _fund_pool;
	tbhash_t _hash;
	tbglobal_t _global;
	string _strmemo;

	void parse_memo(string memo,
					uint8_t* roll_under,
					capi_checksum256* seed_hash,
					capi_checksum160* user_seed_hash,
					uint64_t* expiration,
		                	name* referrer,
					capi_signature* sig) {
		// remove space
		memo.erase(std::remove_if(memo.begin(),
								  memo.end(),
								  [](unsigned char x) { return std::isspace(x); }),
				   memo.end());

		size_t sep_count = std::count(memo.begin(), memo.end(), '-');
		eosio_assert(sep_count == 5, "invalid memo");

		size_t pos;
		string container;
		pos = sub2sep(memo, &container, '-', 0, true);
		eosio_assert(!container.empty(), "no roll under");
		*roll_under = stoi(container);
		pos = sub2sep(memo, &container, '-', ++pos, true);
		eosio_assert(!container.empty(), "no seed hash");
		*seed_hash = hex_to_sha256(container);
		pos = sub2sep(memo, &container, '-', ++pos, true);
		eosio_assert(!container.empty(), "no user seed hash");
		*user_seed_hash = hex_to_sha1(container);
		pos = sub2sep(memo, &container, '-', ++pos, true);
		eosio_assert(!container.empty(), "no expiration");
		*expiration = stoull(container);
		pos = sub2sep(memo, &container, '-', ++pos, true);
		eosio_assert(!container.empty(), "no referrer");
		*referrer = eosio::name(string_to_name(container.c_str()));
		container = memo.substr(++pos);
		eosio_assert(!container.empty(), "no capi_signature");
		*sig = str_to_sig(container);
	}

	uint8_t compute_random_roll(const capi_checksum256& seed1, const capi_checksum160& seed2) {
		size_t hash = 0;
		hash_combine(hash, sha256_to_hex(seed1));
		hash_combine(hash, sha1_to_hex(seed2));
		return hash % 100 + 1;
	}

	asset compute_referrer_reward(const stbet& bet) { return bet.amount / 200; }

	uint64_t next_id() {
		stglobal global = _global.get_or_default(
				stglobal{.current_id = _bets.available_primary_key()});
		global.current_id += 1;
		_global.set(global, _self);
		return global.current_id;
	}

	string referrer_memo(const stbet& bet) {
		string memo = "bet id:";
		string id = uint64_string(bet.id);
		memo.append(id);
		memo.append(" player: ");
		string player = name{bet.player}.to_string();
		memo.append(player);
		memo.append(" referral reward! - dapp.pub/dice/");
		return memo;
	}

	string winner_memo(const stbet& bet) {
		string memo = "bet id:";
		string id = uint64_string(bet.id);
		memo.append(id);
		memo.append(" player: ");
		string player = name{bet.player}.to_string();
		memo.append(player);
		memo.append(" winner! - dapp.pub/dice/");
		return memo;
	}

	stbet find_or_error(const uint64_t& id) {
		auto itr = _bets.find(id);
		eosio_assert(itr != _bets.end(), "bet not found");
		return *itr;
	}

	void assert_hash(const capi_checksum256& seed_hash, const uint64_t& expiration) {
		const uint32_t _now = now();

		// check expiratin
		eosio_assert(expiration > _now, "seed hash expired");

		// check hash duplicate
		const uint64_t key = uint64_hash(seed_hash);
		auto itr = _hash.find(key);
		eosio_assert(itr == _hash.end(), "hash duplicate");

		// clean up
		auto index = _hash.get_index<"expiration"_n>();
		auto upper_itr = index.upper_bound(_now);
		auto begin_itr = index.begin();
		auto count = 0;
		while ((begin_itr != upper_itr) && (count < 3)) {
			begin_itr = index.erase(begin_itr);
			count++;
		}

		// save hash
		_hash.emplace(_self, [&](sthash& r) {
			r.hash = seed_hash;
			r.expiration = expiration;
		});
	}

	void assert_quantity(const asset& quantity) {
		eosio_assert(quantity.symbol == EOS_SYMBOL, "only EOS token allowed");
		eosio_assert(quantity.is_valid(), "quantity invalid");
		eosio_assert(quantity.amount >= 1000, "transfer quantity must be greater than 0.1");
	}

	void assert_roll_under(const uint8_t& roll_under, const asset& quantity) {
		eosio_assert(roll_under >= 2 && roll_under <= 96,
					 "roll under overflow, must be greater than 2 and less than 96");
//		eosio_assert(
//				max_payout(roll_under, quantity) <= max_bonus(),
//				"offered overflow, expected earning is greater than the maximum bonus");
	}

	void save(const stbet& bet) {
		_bets.emplace(_self, [&](stbet& r) {
			r.id = bet.id;
			r.player = bet.player;
			r.referrer = bet.referrer;
			r.amount = bet.amount;
			r.roll_under = bet.roll_under;
			r.seed_hash = bet.seed_hash;
			r.user_seed_hash = bet.user_seed_hash;
			r.created_at = bet.created_at;
		});
		print(">> Saving bet:", bet.id);
	}

	void remove(const stbet& bet) { 
		print(">> Removing bet:", bet.id);
		_bets.erase(bet); }

	void unlock(const asset& amount) {

		stfundpool pool = get_fund_pool();
		pool.locked -= amount;
		eosio_assert(pool.locked.amount >= 0, "fund unlock error");
		_fund_pool.set(pool, _self);
	}

	void lock(const asset& amount) {
		stfundpool pool = get_fund_pool();
		pool.locked += amount;
		_fund_pool.set(pool, _self);
	}

	asset compute_payout(const uint8_t& roll_under, const asset& offer) {
		return min(max_payout(roll_under, offer), max_bonus());
	}
	asset max_payout(const uint8_t& roll_under, const asset& offer) {
		const double ODDS = 98.0 / ((double)roll_under - 1.0);
		return asset(ODDS * offer.amount, offer.symbol);
	}

	asset max_bonus() { return available_balance() / 100; }

	asset available_balance() {
//		auto token = eosio::token(name("eosio.token"));
//nst asset balance =
//token.get_balance(_self, symbol_type(EOS_SYMBOL).name());
//		const asset locked = get_fund_pool().locked;
//		const asset available = balance - locked;
//		eosio_assert(available.amount >= 0, "fund pool overdraw");
		print(">> balance:", _self, ", ");
		return  asset(1000000000, EOS_SYMBOL);
//		return available;
	}

	stfundpool get_fund_pool() {
		stfundpool fund_pool{.locked = asset(0, EOS_SYMBOL)};
		return _fund_pool.get_or_create(_self, fund_pool);
	}

	void assert_signature(const uint8_t& roll_under,
						  const capi_checksum256& seed_hash,
						  const uint64_t& expiration,
						  const name& referrer,
						  const capi_signature& sig) {
		string data = uint64_string(roll_under);
		data += "-";
		data += sha256_to_hex(seed_hash);
		data += "-";
		data += uint64_string(expiration);
		data += "-";
		data += name{referrer}.to_string();

		capi_checksum256 digest;
		const char* data_cstr = data.c_str();
		sha256(data_cstr, strlen(data_cstr), &digest);
		capi_public_key key = str_to_pub(PUB_KEY, false);
		assert_recover_key(&digest,
						   (char*)&sig.data,
						   sizeof(sig.data),
						   (char*)&key.data,
						   sizeof(key.data));
	}

	void assert_seed(const capi_checksum256& seed, const capi_checksum256& hash) {
		string seed_str = sha256_to_hex(seed);
		assert_sha256(seed_str.c_str(),
					  strlen(seed_str.c_str()),
					  (const capi_checksum256*)&hash);
	}

	template <typename... Args>
	void send_defer_action(Args&&... args) {
		transaction trx;
		trx.actions.emplace_back(std::forward<Args>(args)...);
		trx.send(next_id(), _self, false);
	}
};
//EOSIO_DISPATCH( newn, (hil)(receipt)(reveal)(transfer)(droptable))

 extern "C" {
 void apply(uint64_t receiver, uint64_t code, uint64_t action) {
	print(">> apply receiver:", name(receiver), ", code:", name(code), ", action:", name(action));

 	if ((name(code) == name("eosio.token")) && (name(action) == name("transfer"))) {
		eosio::execute_action( eosio::name(receiver), eosio::name(code), &newn::transfer );
 		return;
	}

 	if (code != receiver) return;
 	switch (action) { EOSIO_DISPATCH_HELPER(newn, (receipt)(reveal)(hil)(droptable) ) };
 	eosio_exit(0);
     }
 }
